package com.cat.orm.core.db.process;

import java.util.*;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

import com.cat.orm.core.annotation.DataConverter;
import com.cat.orm.core.annotation.PO;
import com.cat.orm.core.base.IBaseData;
import com.cat.orm.core.db.dao.CommonDao;
import com.cat.orm.core.db.dao.ConvertDao;
import com.cat.orm.core.db.dao.DelayConvertDao;
import com.cat.orm.core.db.dao.IDao;
import com.cat.orm.core.db.factory.CommonDaoFactory;
import com.cat.orm.core.db.factory.ConvertDaoFactory;
import com.cat.orm.core.db.factory.DaoFactory;
import com.cat.orm.core.db.factory.DelayConvertDaoFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.jdbc.core.JdbcTemplate;

/**
 * 数据操作器
 */
public class DataProcessor implements IDataProcess {

	private static final Logger log = LoggerFactory.getLogger(DataProcessor.class);

	/**
	 * 由DataProcessor代理的Dao, 只能从DataProcessor内获取 可以由spring管理, 通过注解去获取 key: class
	 * name, 类名 value: dao 存储dao
	 */
	private Map<String, IDao<? extends IBaseData>> commonDaoMap = new HashMap<>();

	protected ScheduledExecutorService executor = null;

	/**
	 * 工厂map
	 */
	private Map<Class<? extends IDao>, DaoFactory> factoryMap = new HashMap<>();

	@SuppressWarnings("unchecked")
	public <T extends IBaseData> IDao<T> getCommonDao(String name) {
		if (name == null || name.isEmpty()) {
			throw new NullPointerException("name is can not be null:" + name);
		}
		return (IDao<T>) commonDaoMap.get(name);
	}

	public <T extends IBaseData> IDao<T> getCommonDao(Class<T> clazz) {
		if (clazz == null) {
			throw new NullPointerException("clazz is can not be null:" + clazz);
		}
		String name = clazz.getSimpleName().toLowerCase();
		return getCommonDao(name);
	}

	public <T extends IBaseData> IDao<T> getCommonDao(T clazz) {
		if (clazz == null) {
			throw new NullPointerException("clazz is can not be null:" + clazz);
		}
		return getCommonDao(clazz.getClass().getSimpleName().toLowerCase());
	}

	public DataProcessor() {
		this.initFactory(Collections.emptyList());
		executor = Executors.newSingleThreadScheduledExecutor();
		this.executor.scheduleAtFixedRate(this::tick, 1, 1, TimeUnit.MINUTES);
	}

	public DataProcessor(List<DaoFactory> factories) {
		this.initFactory(factories);
		//this.initDataProcessor(baseClazzList, jdbcTemplate);
		executor = Executors.newSingleThreadScheduledExecutor();
		this.executor.scheduleAtFixedRate(this::tick, 1, 1, TimeUnit.MINUTES);
	}

	private void initFactory(List<DaoFactory> factories) {
		//内置的dao
		factoryMap.put(CommonDao.class, new CommonDaoFactory());
		factoryMap.put(ConvertDao.class, new ConvertDaoFactory());
		factoryMap.put(DelayConvertDao.class, new DelayConvertDaoFactory());
		//开发者自定义dao
		for (DaoFactory factory : factories) {
			factoryMap.put(factory.daoClazz(), factory);
		}
	}

	/**
	 * 初始化数据
	 */
	public void initDataProcessor(Collection<Class<IBaseData>> baseClazzList, JdbcTemplate jdbcTemplate){
		for (Class<IBaseData> clazz : baseClazzList) {
            try {
                this.register(clazz, jdbcTemplate);
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        }
	}

	private <T extends IBaseData> void register(Class<T> clazz, JdbcTemplate jdbcTemplate) throws Exception {
		PO tableNameAnnotation = clazz.getAnnotation(PO.class);
		if (tableNameAnnotation == null) {
			throw new IllegalArgumentException("beanClazz[" + clazz.getName() + "] tableName is null.");
		}
		String tableNameValue = tableNameAnnotation.name();
		if (tableNameValue == null || tableNameValue.isEmpty()) {
			throw new NullPointerException("beanClazz[" + clazz.getName() + "] tableName is null.");
		}
		DataConverter dataConverter = clazz.getAnnotation(DataConverter.class);
		Class<? extends IDao> daoClass = dataConverter != null ? dataConverter.dao() : CommonDao.class;

		DaoFactory factory = factoryMap.get(daoClass);
		if (factory == null) {
			throw new IllegalArgumentException("No factory registered for DAO class: " + daoClass.getName());
		}
		IDao<T> dao = factory.createDao(clazz, jdbcTemplate, tableNameValue);
		log.info("DataProcessor register, clazz:{}, dao:{}", clazz.getSimpleName(), dao);
		commonDaoMap.put(clazz.getSimpleName().toLowerCase(), dao); // 注册DAO
	}

	/**
	 * 查询信息
	 * 
	 * @date 2020年6月30日
	 * @param clazz
	 * @return
	 */
	public <T extends IBaseData> List<T> selectAll(Class<T> clazz) {
		IDao<T> dao = getCommonDao(clazz);
		if (dao == null) {
			throw new NullPointerException("Can not find dao by the clazz:" + clazz);
		}
        try {
            return (List<T>) dao.selectAll();
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

	/**
	 * 查询信息
	 * 
	 * @date 2020年6月30日
	 * @param clazz 查询对象
	 * @param value 主键
	 * @return 对象结果
	 */
	public <T extends IBaseData> T selectByPrimaryKey(Class<T> clazz, Object value) {
		IDao<T> dao = getCommonDao(clazz);
		if (dao == null) {
			throw new NullPointerException("Can not find dao by the clazz:" + clazz);
		}
        try {
            return dao.selectByKey(value);
        } catch (Exception e) {
			log.error("selectByPrimaryKey error, clazz:{}, value:{}", clazz, value, e);
            throw new RuntimeException(e);
        }
    }

	/**
	 * 查询玩家信息, 通过默认索引,这种方式直接获取缓存的sql进行查询
	 * 
	 * @date 2020年6月30日
	 * @param clazz
	 * @return
	 */
	public <T extends IBaseData> List<T> selectByIndex(Class<T> clazz, Object[] objs) {
		IDao<T> dao = getCommonDao(clazz);
		if (dao == null) {
			throw new NullPointerException("Can not find dao by the clazz:" + clazz);
		}
        try {
            return (List<T>) dao.selectByIndex(objs);
        } catch (Exception e) {
			log.error("selectByIndex error, clazz:{}, value:{}", clazz, Arrays.toString(objs), e);
            throw new RuntimeException(e);
        }
    }

	/**
	 * 查询玩家信息, 通过默认索引,这种方式直接获取缓存的sql进行查询
	 * 
	 * @date 2020年6月30日
	 * @param clazz
	 * @return
	 */
	public <T extends IBaseData> T selectOneByIndex(Class<T> clazz, Object[] objs) {
		List<T> ret = this.selectByIndex(clazz, objs);
		if (ret == null || ret.isEmpty()) {
			return null;
		}
		return ret.get(0);
	}

	/**
	 * 查询玩家信息, 通过制定字段,这种方式只能通过sql组装进行查询
	 * 
	 * @date 2020年6月30日
	 * @param clazz
	 * @return
	 */
	public <T extends IBaseData> List<T> selectByIndex(Class<T> clazz, String[] props, Object[] objs) {
		IDao<T> dao = getCommonDao(clazz);
		if (dao == null) {
			throw new NullPointerException("Can not find dao by the props:" + Arrays.toString(props));
		}
        try {
            return (List<T>) dao.selectByIndex(props, objs);
        } catch (Exception e) {
			log.error("selectByIndex error, clazz:{}, props:{}, value:{}", clazz, Arrays.toString(props), Arrays.toString(objs), e);
            throw new RuntimeException(e);
        }
    }

	/**
	 * 添加玩家信息
	 * 
	 * @date 2020年6月30日
	 * @param po
	 */
	public <T extends IBaseData> int insert(T po) {
		IDao<T> dao = getCommonDao(po);
		if (dao == null) {
			throw new NullPointerException("Can not find dao by the Pojo:" + po);
		}
		return dao.insert(po);
	}

	/**
	 * 批量添加
	 * 
	 * @date 2020年6月30日
	 * @param basePos
	 */
	public <T extends IBaseData> void insertBatch(Collection<T> basePos) {
		Map<String, List<T>> map = splitData(basePos);
		IDao<T> dao = null;
		for (String name : map.keySet()) {
			dao = getCommonDao(name);
			dao.insertBatch(map.get(name));
		}
		map = null;
	}

	/**
	 * 添加玩家信息
	 * 
	 * @date 2020年6月30日
	 * @param po
	 */
	public <T extends IBaseData> int replace(T po) {
		IDao<T> dao = getCommonDao(po);
		if (dao == null) {
			throw new NullPointerException("Can not find dao by the Pojo:" + po);
		}
		return dao.replace(po);
	}

	/**
	 * 添加玩家信息
	 * 
	 * @date 2020年6月30日
	 * @param po
	 */
	public <T extends IBaseData> int update(T po) {
		IDao<T> dao = getCommonDao(po);
		if (dao == null) {
			throw new NullPointerException("Can not find dao by the Pojo:" + po);
		}
		return dao.update(po);
	}

	/**
	 * 批量修改玩家信息
	 * @date 2020年6月30日
	 * @param pos 数据集合
	 */
	public <T extends IBaseData> int updateBatch(Collection<T> pos) {
		Map<String, List<T>> map = splitData(pos);
		IDao<T> dao = null;
		for (String name : map.keySet()) {
			dao = getCommonDao(name);
			dao.updateBatch(map.get(name));
		}
		return 0;
	}

	/**
	 * 添加玩家信息
	 * 
	 * @date 2020年6月30日
	 * @param po 数据对象
	 */
	public <T extends IBaseData> int delete(T po) {
		IDao<T> dao = getCommonDao(po);
		if (dao == null) {
			throw new NullPointerException("Can not find dao by the Pojo:" + po);
		}
		return dao.delete(po);
	}

	/**
	 * 删除所有
	 * @date 2020年6月30日
	 * @param clazz 要删掉所有数据
	 */
	public <T extends IBaseData> void deleteAll(Class<T> clazz) {
		IDao<T> dao = getCommonDao(clazz);
		if (dao == null) {
			throw new NullPointerException("Can not find dao by the clazz:" + clazz);
		}
		dao.deleteAll();
	}

	/**
	 * 批量删除
	 * @date 2020年6月30日
	 * @param basePos 要删掉的指定数据
	 */
	public <T extends IBaseData> void deleteBatch(List<T> basePos) {
		Map<String, List<T>> map = splitData(basePos);
		IDao<T> dao = null;
		for (String name : map.keySet()) {
			dao = getCommonDao(name);
			dao.deleteBatch(map.get(name));
		}
		map = null;
	}
	
	/**
	 * 通过指定sql查询. 虽说指定了sql语句,但需要通过clazz获取处理dao
	 * 
	 * @date 2020年6月30日
	 * @param clazz 操作对象
	 * @param sql 要执行的sql
	 */
	public <T extends IBaseData> List<T> selectBySql(Class<T> clazz, String sql) {
		IDao<T> dao = getCommonDao(clazz);
		if (dao == null) {
			throw new NullPointerException("Can not find dao by the clazz:" + clazz);
		}
        try {
            return (List<T>) dao.selectBySql(sql);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

	/**
	 * 数据分类
	 * 
	 * @date 2020年8月5日
	 * @param basePos
	 * @return
	 */
	protected <T extends IBaseData> Map<String, List<T>> splitData(Collection<T> basePos) {
		// 数据分类
		Map<String, List<T>> map = new HashMap<String, List<T>>();
		for (T po : basePos) {
			String name = po.getClass().getSimpleName().toLowerCase();
			List<T> list = map.get(name);
			if (list == null) {
				list = new ArrayList<T>();
				map.put(name, list);
			}
			list.add(po);
		}
		return map;
	}
	
	@Override
	public <T extends IBaseData> void execute(Class<T> clazz, String sql) {
		IDao<T> dao = getCommonDao(clazz);
		if (dao == null) {
			throw new NullPointerException("Can not find dao by the clazz:" + clazz);
		}
		dao.execute(sql);
	}

	/**
	 * 定时tick保存数据
	 */
	public void tick(){
		for (IDao<? extends IBaseData> dao : commonDaoMap.values()) {
			if (dao instanceof DelayConvertDao){
				((DelayConvertDao<?, ?>) dao).tick();
			}
		}
	}
}
